#include "t_bend_yt_para.h"

class T_HttpAccessReply_YoutubeParallelPlayback;
class T_HttpAccessReply_YoutubeParallelPlaybackPrivate;

//[PartialReply]
class T_HttpAccessReply_YoutubeParallelPlayback_PartialReply : public QObject
{
    Q_OBJECT
public:
    explicit T_HttpAccessReply_YoutubeParallelPlayback_PartialReply(
            T_HttpAccessBackend *a_backend,
            const QNetworkRequest &a_request,
            qint64 a_begin,
            qint64 a_length = -1)
        : m_backend(a_backend), m_request(a_request), m_begin(a_begin), m_length(a_length)
        , m_reply(0), m_waiter(0), m_read_count(0), m_name("")
    {
        Q_ASSERT(m_begin>=0);
        Q_ASSERT(m_length!=0);
        m_nam.setCookieJar(0);
    }
    virtual ~T_HttpAccessReply_YoutubeParallelPlayback_PartialReply()
    {
        m_waiter->deleteLater();
        m_reply->deleteLater();
    }
    bool isActive()
    {
        return (m_waiter != NULL);
    }
    bool isFinished()
    {
        if(m_waiter == NULL)
        {
            return false;
        }
        return (m_waiter->isFinished());
    }
    qint64 bytesAvailable()
    {
        if(m_waiter == NULL)
        {
            return 0;
        }
        return (m_waiter->just_bytesAvailable());
    }
    QByteArray wait_readAll(qint64 a_break_size)
    {
        QByteArray v_result;
        if(m_waiter == NULL)
        {
            v_result = QByteArray();
        }
        else
        {
            v_result = m_waiter->wait_readAll(a_break_size);
        }
        m_read_count += v_result.size();
        return v_result;
    }
    QByteArray just_read(qint64 a_max)
    {
        QByteArray v_result;
        if(m_waiter == NULL)
        {
            v_result = QByteArray();
        }
        else
        {
            v_result = m_waiter->just_read(a_max);
        }
        m_read_count += v_result.size();
        return v_result;
    }
    void wake()
    {
        if(this->isActive())
        {
            return;
        }
        //[Range header]
        QString v_range = QString("bytes=%1-").arg(m_begin);
        if(m_length > 0)
        {
            v_range += QString::number(m_begin+m_length-1);
        }
        //qDebug() << v_range;
        m_name = v_range;
        m_request.setRawHeader("Range", v_range.toLatin1());
        //
        if(m_reply)
        {
            m_reply->disconnect((QObject *)0, 0);
            m_reply->deleteLater();
        }
#if 0x0
        m_reply = m_backend->networkAccessManager()->createRequestWithBackend(
                QNetworkAccessManager::GetOperation,
                m_request,
                0,
                0);
#else
        m_reply = m_nam.createRequestWithBackend(
                QNetworkAccessManager::GetOperation,
                m_request,
                0,
                0);
#endif
        m_waiter = new NetworkBuffer(m_reply);
        m_waiter->setName(QString("waiter@%1").arg(m_begin));
        connect(m_reply, SIGNAL(metaDataChanged()), this, SLOT(on_reply_metaDataChanged()));
        connect(m_reply, SIGNAL(readyRead()), this, SLOT(on_reply_readyRead()));
        connect(m_reply, SIGNAL(finished()), this, SLOT(on_reply_finished()));
    }
    QString name()
    {
        return m_name;
    }

public slots:
    void on_reply_metaDataChanged()
    {
        Q_ASSERT(m_reply);
        QVariant v_loc = m_reply->header(QNetworkRequest::LocationHeader);
        if(v_loc.isValid())
        {
            qDebug() << "[v_loc]" << v_loc;
            m_waiter->deleteLater();;
            m_waiter = NULL;
            m_reply->deleteLater();
            m_reply = NULL;
            m_request.setUrl(v_loc.toUrl());
            qDebug() << "[goto try_again]";
            this->wake();
        }
    }
    void on_reply_readyRead()
    {
        Q_ASSERT(m_reply);
        QVariant v_loc = m_reply->header(QNetworkRequest::LocationHeader);
        if(!v_loc.isValid())
        {
            emit readyRead();
        }
    }
    void on_reply_finished()
    {
        Q_ASSERT(m_reply);
        QVariant v_loc = m_reply->header(QNetworkRequest::LocationHeader);
        if(!v_loc.isValid())
        {
            if(m_length>0)
            {
                //Q_ASSERT( m_length==(m_read_count+m_waiter->just_bytesAvailable()) );
                if( m_length!=(m_read_count+m_waiter->just_bytesAvailable()) )
                {
                    if(m_read_count+m_waiter->just_bytesAvailable()==0)
                    {
                        m_waiter->deleteLater();;
                        m_waiter = NULL;
                        m_reply = NULL;
                        //m_request.setUrl(v_loc.toUrl());
                        qDebug() << "[goto try_again](2)";
                        this->wake();
                        return;
                    }
                    qDebug() << m_reply->header(QNetworkRequest::ContentTypeHeader);
                    qDebug() << m_reply->header(QNetworkRequest::ContentLengthHeader);
                    qDebug() << "[m_begin]" << m_begin;
                    qDebug() << "[m_length]" << m_length;
                    qDebug() << "[m_read_count]" << m_read_count;
                    qDebug() << "[m_waiter->just_bytesAvailable()]" << m_waiter->just_bytesAvailable();
                    //Q_ASSERT( m_length==(m_read_count+m_waiter->just_bytesAvailable()) );
                    qint64 v_part_len = (m_read_count+m_waiter->just_bytesAvailable());
                    QString v_range = QString("bytes=%1-").arg(m_begin+v_part_len);
                    if(m_length > 0)
                    {
                        v_range += QString::number(m_begin+m_length-1-v_part_len);
                    }
                    //qDebug() << v_range;
                    //m_name = v_range;
                    m_request.setRawHeader("Range", v_range.toLatin1());
                    if(m_reply) m_reply->deleteLater();
#if 0x0
                    m_reply = m_backend->networkAccessManager()->createRequestWithBackend(
                            QNetworkAccessManager::GetOperation,
                            m_request,
                            0,
                            0);
#else
                    m_reply = m_nam.createRequestWithBackend(
                            QNetworkAccessManager::GetOperation,
                            m_request,
                            0,
                            0);
#endif
                    m_waiter->setNewReply(m_reply);
                    connect(m_reply, SIGNAL(metaDataChanged()), this, SLOT(on_reply_metaDataChanged()));
                    connect(m_reply, SIGNAL(readyRead()), this, SLOT(on_reply_readyRead()));
                    connect(m_reply, SIGNAL(finished()), this, SLOT(on_reply_finished()));
                    qDebug() << "[goto try_again](3)";
                }
            }
            //qDebug() << "FINISH:" << m_name << m_length << m_read_count << m_waiter->bytesAvailable();
            if(m_waiter->just_bytesAvailable())
            {
                emit readyRead();
                //qDebug() << "FINISH(2):" << m_name << m_length << m_read_count << m_waiter->bytesAvailable();
            }
            emit finished();
        }
    }
protected:
    T_HttpAccessBackend *m_backend;
    QNetworkRequest m_request;
    qint64 m_begin;
    qint64 m_length;
    QNetworkReply *m_reply;
    NetworkBuffer *m_waiter;
    qint64 m_read_count;
    QString m_name;
    T_NetworkAccessManager m_nam;
signals:
    void readyRead();
    void finished();
};

//[T_HttpAccessReply]
class T_HttpAccessReply_YoutubeParallelPlayback : public T_HttpAccessReply
{
    Q_OBJECT
public:
    QNetworkReply *m_headReply;
    explicit T_HttpAccessReply_YoutubeParallelPlayback(T_HttpAccessBackend *backend);
    //
    virtual void start(T_NetworkAccessManager * nam,
                       QNetworkAccessManager::Operation op,
                       const QNetworkRequest & request,
                       QIODevice * outgoingData);
    // raw headers:
    virtual bool hasRawHeader(const QByteArray &headerName) const;
    virtual QList<QByteArray> rawHeaderList() const;
    virtual QByteArray rawHeader(const QByteArray &headerName) const;
    // attributes
    virtual QVariant attribute(QNetworkRequest::Attribute code) const;
    // reading
    virtual qint64 bytesAvailable();
    virtual QByteArray read(qint64 a_max);
    class T_HttpAccessReply_YoutubeParallelPlaybackPrivate *d_ptr;
    Q_DECLARE_PRIVATE(T_HttpAccessReply_YoutubeParallelPlayback);
public slots:
    void self_on_head_reply_metaDataChanged();
    void self_on_part_reply_readyRead();
    void self_on_part_reply_finished();
};

//[T_HttpAccessReply <Private>]
class T_HttpAccessReply_YoutubeParallelPlaybackPrivate : public QObject
{
public:
    T_HttpAccessReply_YoutubeParallelPlaybackPrivate(T_HttpAccessReply_YoutubeParallelPlayback *me)
        : QObject(me), q_ptr(me)
    {
        v_thread_count = 8;
        //v_thread_count = 16;
        //v_thread_count = 1;
        //v_divided_size = 500000; //500KB
        //v_divided_size = 409600;
        v_divided_size = 250000; //250KB
    }
    Q_DECLARE_PUBLIC(T_HttpAccessReply_YoutubeParallelPlayback)
    class T_HttpAccessReply_YoutubeParallelPlayback *q_ptr;
    int v_thread_count;
    int v_divided_size;
    T_NormalPointerQueue<T_HttpAccessReply_YoutubeParallelPlayback_PartialReply> m_reply_queue;
    void gc()
    {
        while(m_reply_queue.size()>0)
        {
            if(!m_reply_queue.head()->isFinished()) break;
            if(m_reply_queue.head()->bytesAvailable()>0) break;
            m_reply_queue.dequeue()->deleteLater();
        }
    }
    void wakeReplyQueue()
    {
        int v_size = qMin(v_thread_count, m_reply_queue.size());
        for(int i=0; i<m_reply_queue.size(); i++)
        {
            if(v_size==0) break;
            if(!m_reply_queue.value(i)->isFinished())
            {
                m_reply_queue.value(i)->wake();
                v_size--;
            }
        }
    }
    void adjustQueue()
    {
        this->gc();
        wakeReplyQueue();
    }
    bool isFinished()
    {
        for(int i=0; i<m_reply_queue.size(); i++)
        {
            bool v_finished = (m_reply_queue.value(i)->isFinished());
            //v_finished &= (m_reply_queue.head()->bytesAvailable()==0);
            if(!v_finished) return false;
        }
        return true;
    }
    qint64 bytesAvailable()
    {
        this->gc();
        qint64 v_total = 0;
        for(int i=0; i<m_reply_queue.size(); i++)
        {
            bool v_finished = m_reply_queue.value(i)->isFinished();
            v_total += m_reply_queue.value(i)->bytesAvailable();
            if(!v_finished) break;
        }
        //qDebug() << "[d->bytesAvailable()]" << v_total;
        return v_total;
    }
    QByteArray read(qint64 a_max)
    {
        this->gc();
        QByteArray v_total;
        qint64 v_length = a_max;
        for(int i=0; i<m_reply_queue.size(); i++)
        {
            if(v_length<=0) break;
            bool v_finished = m_reply_queue.value(i)->isFinished();
            QByteArray v_bytes = m_reply_queue.value(i)->just_read(v_length);
            v_total.append(v_bytes);
            v_length -= v_bytes.size();
            if(!v_finished) break;
        }
        Q_ASSERT(v_total.size()<=a_max);
        return v_total;
    }
};

T_HttpAccessReply_YoutubeParallelPlayback::T_HttpAccessReply_YoutubeParallelPlayback(
        T_HttpAccessBackend *parent)
    : T_HttpAccessReply(parent)
    , m_headReply(0)
{
    this->d_ptr = new T_HttpAccessReply_YoutubeParallelPlaybackPrivate(this);
    Q_D(T_HttpAccessReply_YoutubeParallelPlayback); Q_UNUSED(d);

#if 0x0
    Q_ASSERT(backend()->operation() == QNetworkAccessManager::GetOperation);

    //qDebug() << "[T_HttpReply_3::T_HttpReply_3()]" << m_url;
    // HEAD op
    m_headReply = backend()->networkAccessManager()->createRequestWithBackend(
            backend()->operation(),
            backend()->request(),
            backend()->uploadDevice(),
            0
            );

    connect(m_headReply, SIGNAL(metaDataChanged()), this, SLOT(self_on_head_reply_metaDataChanged()));
#endif
}

void T_HttpAccessReply_YoutubeParallelPlayback::start(T_NetworkAccessManager * nam,
                                                      QNetworkAccessManager::Operation op,
                                                      const QNetworkRequest & request,
                                                      QIODevice * outgoingData)
{
    Q_ASSERT(backend()->operation() == QNetworkAccessManager::GetOperation);

    //qDebug() << "[T_HttpReply_3::T_HttpReply_3()]" << m_url;
    // HEAD op
    m_headReply = backend()->networkAccessManager()->createRequestWithBackend(
            backend()->operation(),
            backend()->request(),
            backend()->uploadDevice(),
            0
            );

    connect(m_headReply, SIGNAL(metaDataChanged()), this, SLOT(self_on_head_reply_metaDataChanged()));
}


void T_HttpAccessReply_YoutubeParallelPlayback::self_on_head_reply_metaDataChanged()
{
    Q_D(T_HttpAccessReply_YoutubeParallelPlayback);

    QVariant v_content_type = m_headReply->header(QNetworkRequest::ContentTypeHeader);
    QVariant v_content_len = m_headReply->header(QNetworkRequest::ContentLengthHeader);
    qDebug()<< "[T_HttpAccessReply_YoutubeParallelPlayback::self_on_head_reply_metaDataChanged()]"
            << v_content_type << v_content_len;
    if(
            !v_content_type.toString().startsWith("video/")
            ||
            !v_content_len.isValid()
            )
    {
        enqueue_metaDataChanged();
        if(m_headReply->bytesAvailable())
        {
            enqueue_readyRead();
        }
        if(m_headReply->isFinished())
        {
            enqueue_finished();
            return;
        }
        p_connectQNetworkReply(m_headReply);
        return;
    }

#if 0x1
    QVariant v_loc = m_headReply->header(QNetworkRequest::LocationHeader);
    Q_ASSERT(!v_loc.isValid());
#endif

    m_headReply->close();

    qDebug() << backend()->request().hasRawHeader("Range");
    QByteArray v_range = backend()->request().rawHeader("Range");
    qDebug() << v_range.size();
    QString v_range_st = QString::fromLatin1(v_range);
    qDebug() << v_range_st;
    QRegExp v_range_rx("^bytes=([0-9]+)-");

    if(!v_content_len.isValid() || d->v_thread_count == 1)
    {
        d->m_reply_queue.enqueue(new T_HttpAccessReply_YoutubeParallelPlayback_PartialReply(backend(), backend()->request(), 0, -1));
    }
    else
    {
        qint64 v_offset = 0;
        if(v_range_rx.indexIn(v_range_st) != -1)
        {
            QString v_cap_1 = v_range_rx.cap(1);
            qDebug() << "[v_cap_1]" << v_cap_1;
            v_offset =  v_cap_1.toLongLong();
            qDebug() << "[v_offset]" << v_offset;
        }
        qint64 v_total = v_content_len.toLongLong();
        while(v_total > 0)
        {
            qint64 v_length = qMin((qint64)d->v_divided_size, v_total);
            d->m_reply_queue.enqueue(new T_HttpAccessReply_YoutubeParallelPlayback_PartialReply(backend(), backend()->request(), v_offset, v_length));
            v_offset += v_length;
            v_total -= v_length;
        }
    }
    for(int i=0; i<d->m_reply_queue.size(); i++)
    {
        T_HttpAccessReply_YoutubeParallelPlayback_PartialReply *v_part_reply = d->m_reply_queue.value(i);
        connect(v_part_reply, SIGNAL(readyRead()), this, SLOT(self_on_part_reply_readyRead()));
        connect(v_part_reply, SIGNAL(finished()), this, SLOT(self_on_part_reply_finished()));
    }

    d->adjustQueue();

    //emit metaDataChanged(); //N.G.
    enqueue_metaDataChanged();
}

// raw headers:
/* virtual */ bool T_HttpAccessReply_YoutubeParallelPlayback::hasRawHeader(const QByteArray &headerName) const
{
    return m_headReply->hasRawHeader(headerName);
}
/* virtual */ QList<QByteArray> T_HttpAccessReply_YoutubeParallelPlayback::rawHeaderList() const
{
    return m_headReply->rawHeaderList();
}
/* virtual */ QByteArray T_HttpAccessReply_YoutubeParallelPlayback::rawHeader(const QByteArray &headerName) const
{
    return m_headReply->rawHeader(headerName);
}

// attributes
/* virtual */ QVariant T_HttpAccessReply_YoutubeParallelPlayback::attribute(QNetworkRequest::Attribute code) const
{
    return m_headReply->attribute(code);
}

// reading
/* virtual */ qint64 T_HttpAccessReply_YoutubeParallelPlayback::bytesAvailable()
{
    Q_D(T_HttpAccessReply_YoutubeParallelPlayback);
    return d->bytesAvailable();
}
/* virtual */ QByteArray T_HttpAccessReply_YoutubeParallelPlayback::read(qint64 a_max)
{
    Q_D(T_HttpAccessReply_YoutubeParallelPlayback);
    return d->read(a_max);
}

void T_HttpAccessReply_YoutubeParallelPlayback::self_on_part_reply_readyRead()
{
    Q_D(T_HttpAccessReply_YoutubeParallelPlayback);
    //qDebug() << "[T_HttpReply_3::head_readyRead()]" << d->bytesAvailable() << d->m_reply_queue.size();
    if(d->bytesAvailable()>0)
    {
        //emit readyRead();
        enqueue_readyRead();
    }
}

void T_HttpAccessReply_YoutubeParallelPlayback::self_on_part_reply_finished()
{
    //qDebug() << "[T_HttpReply_3::head_finished()]";
    Q_D(T_HttpAccessReply_YoutubeParallelPlayback);
    d->adjustQueue();
    if(d->bytesAvailable()>0)
    {
        //emit readyRead();
        enqueue_readyRead();
    }
    if(d->isFinished())
    {
        //emit finished();
        enqueue_finished();
    }
}


//[Backend]
bool T_HttpAccessBackend_YoutubeParallelPlayback::canHandle(
        T_NetworkAccessManager * manager,
        QNetworkAccessManager::Operation op,
        const QNetworkRequest & request,
        ////QNetworkRequest request,
        QIODevice * outgoingData)
{
    Q_UNUSED(outgoingData);
    QString v_scheme = request.url().scheme().toLower();
    if(v_scheme != "http" && v_scheme != "https") return false;
    if(op != QNetworkAccessManager::GetOperation) return false;
    if(!request.url().toString().contains(".youtube.com/videoplayback?")) return false;
    return true;
}

//[Backend]
T_HttpAccessReply *T_HttpAccessBackend_YoutubeParallelPlayback::createHttpReply()
{
    Q_ASSERT(T_HttpAccessBackend_YoutubeParallelPlayback::canHandle(
            this->networkAccessManager(),
            this->operation(),
            this->request(),
            this->uploadDevice()));
    return new T_HttpAccessReply_YoutubeParallelPlayback(this);
}

//[Backend Factory]
QObject *T_NetworkAccessBackendFactory_YoutubeParallelPlayback::
        create(T_NetworkAccessManager * manager,
               QNetworkAccessManager::Operation op,
               const QNetworkRequest & request,
               QIODevice * outgoingData) const
{
    if(T_HttpAccessBackend_YoutubeParallelPlayback::canHandle(
            manager,
            op,
            request,
            outgoingData))
    {
        return new T_HttpAccessBackend_YoutubeParallelPlayback(manager);
    }
    return NULL;
}

//[N.A.M.]
T_NetworkAccessManager_YoutubeParallelPlayback::
        T_NetworkAccessManager_YoutubeParallelPlayback(QObject *parent)
            : T_NetworkAccessManager(parent)
{
    this->appendBackendFactory(new T_NetworkAccessBackendFactory_YoutubeParallelPlayback(this));
}

#include "t_bend_yt_para.moc"
